﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Media;
using GammaJul.LgLcd;
using GammaJul.LgLcd.Wpf;

namespace AOC19NET {
	/// <summary>
	/// Application class.
	/// </summary>
	internal class AOC19 : Application {
		#region DEFINITIONS
		/// <summary>
		/// Interval between render calls (in ms)
		/// </summary>
		const double RENDER_INTERVAL = 100.0;

		/// <summary>
		/// Interval between update calls (in ms)
		/// </summary>
		const double RUN_INTERVAL = 500.0;

		/// <summary>
		/// To keep track of the pages.
		/// </summary>
		enum ePages {
			PAGE_WINAMP,
			PAGE_GMAIL,
			PAGE_RIVATUNER,

			NUM_PAGES
		};
		#endregion

		/*-------------------------------------------------------------------------*/
		/* MEMBERS																   */
		/*-------------------------------------------------------------------------*/
		#region MEMBERS
		/// <summary>
		/// Instance of the G19's LCD Library, used mainly for initialization, connection and rendering purposes
		/// </summary>
		private LcdApplet m_LCDApp;

		/// <summary>
		/// Instance of the G19's Device object, needed for handling connections and disconnections.
		/// </summary>
		private LcdDeviceQvga m_LCDDevice;

		/// <summary>
		/// Timer to control the render interval.
		/// </summary>
		private DispatcherTimer m_RenderTimer;

		/// <summary>
		/// Timer to control the update interval.
		/// </summary>
		private DispatcherTimer m_RunTimer;

		/// <summary>
		/// Array containing the multiple pages we'll have
		/// </summary>
		private List<LcdWpfPage> m_vPages;

		/// <summary>
		/// Winamp Page
		/// </summary>
		public WinampPage m_WinampPage;

		/// <summary>
		/// GMail Page
		/// </summary>
		public GMailPage m_GMailPage;

		/// <summary>
		/// RivaTuner Page
		/// </summary>
		public RivaTunerPage m_RivaTunerPage;

		/// <summary>
		/// Configuration Window.
		/// </summary>
		private ConfigurationWindow m_ConfigWindow;

		/// <summary>
		/// Index of the current page.
		/// </summary>
		private int m_iCurrentPage;
		#endregion

		/*-------------------------------------------------------------------------*/
		/* MAIN METHODS															   */
		/*-------------------------------------------------------------------------*/
		#region METHODS
		/// <summary>
		/// On startup, creation of the applet
		/// </summary>
		/// <param name="_EventArgs">Startup event arguments</param>
		protected override void OnStartup(StartupEventArgs _EventArgs) {
			base.OnStartup(_EventArgs);

			// Before do anything, check all settings without default value
			if(AOC19NET.Properties.Settings.Default.RTPageSettings == null) {
				AOC19NET.Properties.Settings.Default.RTPageSettings = new CRivaTunerPageSettings();
			}

			// Initialize our LCD Applet
			m_LCDApp = new LcdApplet("AOC19NET LgLcd WPF", LcdAppletCapabilities.Qvga);
			m_iCurrentPage = 0;
			
			// Initialize the different pages
			m_vPages = new List<LcdWpfPage>();
			m_WinampPage = new WinampPage();
			m_GMailPage = new GMailPage();
			m_RivaTunerPage = new RivaTunerPage();

			// Register to events to know when a device arrives, then connects the applet to the LCD Manager
			m_LCDApp.DeviceArrival += DeviceArrivalCallback;
			m_LCDApp.Connect();

			// Create the configuration window
			m_ConfigWindow = new ConfigurationWindow(this);
			m_ConfigWindow.Visibility = Visibility.Hidden;
		}

		/// <summary>
		/// Check if we already have a device. If don't, we'll create it, else we'll just reopen it.
		/// </summary>
		private void OnQvgaDeviceArrival() {
			// First device arrival, create the device and add a new page with a new instance of our MainControl
			if(m_LCDDevice == null) {
				// Load config from disk
				LoadConfig();

				// Open device
				m_LCDDevice = (LcdDeviceQvga)m_LCDApp.OpenDeviceByType(LcdDeviceType.Qvga);

				// Create our pages and add them to the device
				// Page 1: Winamp
				m_WinampPage.Init();
				m_vPages.Add(new LcdWpfPage(m_LCDDevice) {
					Element = m_WinampPage.m_MainControl
				});

				// Page 2: GMail
				m_GMailPage.Init();
				m_vPages.Add(new LcdWpfPage(m_LCDDevice) {
					Element = m_GMailPage.m_MainControl
				});

				// Page 3: RivaTuner
				m_RivaTunerPage.Init();
				m_vPages.Add(new LcdWpfPage(m_LCDDevice) {
					Element = m_RivaTunerPage.m_MainControl
				});

				// Go to last viewed page
				m_LCDDevice.CurrentPage = m_vPages[AOC19NET.Properties.Settings.Default.CurrentPage];

				// Setup G19 buttons callback
				m_LCDDevice.SoftButtonsChanged += SoftButtonsCallback;

				// Start the timers and set their respective callbacks
				m_RenderTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(RENDER_INTERVAL), DispatcherPriority.Render, RenderCallback, Dispatcher.CurrentDispatcher);
				m_RunTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(RUN_INTERVAL), DispatcherPriority.Render, RunCallback, Dispatcher.CurrentDispatcher);

				// Restore last color scheme
				ApplyColorScheme(AOC19NET.Properties.Settings.Default.BaseColor);
			}

			// Subsequent device arrival means the device was removed and replugged, simply reopen it
			else {
				m_LCDDevice.ReOpen();
			}

			// Do first update after connection
			m_LCDDevice.DoUpdateAndDraw();
		}

		/// <summary>
		/// Load configuration from disk file.
		/// </summary>
		private void LoadConfig() {
			m_iCurrentPage = AOC19NET.Properties.Settings.Default.CurrentPage;
		}

		/// <summary>
		/// Applies a new color scheme to all pages.
		/// </summary>
		/// <param name="_NewBaseColor">The base color for the new color scheme.</param>
		public void ApplyColorScheme(Color _NewBaseColor) {
			m_WinampPage.ChangeColorScheme(_NewBaseColor);
			m_GMailPage.ChangeColorScheme(_NewBaseColor);
			m_RivaTunerPage.ChangeColorScheme(_NewBaseColor);
		}

		/*-------------------------------------------------------------------------*/
		/* UTILS AND AUXILIAR METHODS											   */
		/*-------------------------------------------------------------------------*/
		/// <summary>
		/// Simple utility method to always execute a method on the Application's thread.
		/// </summary>
		/// <param name="_Action">Method to execute.</param>
		private void Invoke(Action _Action) {
			if(CheckAccess()) {
				_Action();
			} else {
				Dispatcher.BeginInvoke(_Action, DispatcherPriority.Render);
			}
		}

		/*-------------------------------------------------------------------------*/
		/* CALLBACKS															   */
		/*-------------------------------------------------------------------------*/
		#region CALLBACKS
		/// <summary>
		/// This event handler will be called whenever a new device of a given type arrives in the system.
		/// This is where we should open the device we want to show the applet on.
		/// Take special care for thread-safety as the SDK calls this handler in another thread.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_EventArgs">Event arguments</param>
		private void DeviceArrivalCallback(object _Sender, LcdDeviceTypeEventArgs _EventArgs) {
			// since with specified LcdAppletCapabilities.Qvga at the Applet's creation, we will only receive QVGA arrival notifications.
			Debug.Assert(_EventArgs.DeviceType == LcdDeviceType.Qvga);
			Invoke(OnQvgaDeviceArrival);
		}

		/// <summary>
		/// Timer tick callback. Updates the LCD screen.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_EventArgs">Event arguments</param>
		private void RenderCallback(object _Sender, EventArgs _EventArgs) {
			if(m_LCDApp.IsEnabled && (m_LCDDevice != null) && !m_LCDDevice.IsDisposed) {
				// Render our pages
				switch(m_iCurrentPage) {
					case (int)ePages.PAGE_WINAMP:
						m_WinampPage.Render();
						m_GMailPage.Render();
						break;

					case (int)ePages.PAGE_GMAIL:
						m_GMailPage.Render();
						break;

					case (int)ePages.PAGE_RIVATUNER:
						m_RivaTunerPage.Render();
						break;
				}

				// Update and draw the LCD;
				m_LCDDevice.DoUpdateAndDraw();
			}
		}

		/// <summary>
		/// Timer tick callback. Updates the logic at the specified interval.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_EventArgs">Event arguments</param>
		private void RunCallback(object _Sender, EventArgs _EventArgs) {
			// Check if the device is connected and ready
			if(m_LCDApp.IsEnabled && (m_LCDDevice != null) && !m_LCDDevice.IsDisposed) {
				// Update our pages
				switch(m_iCurrentPage) {
					case (int)ePages.PAGE_WINAMP:
						m_GMailPage.Run();
						m_WinampPage.Run();
						m_WinampPage.SetUnread(m_GMailPage.GetUnread());
						break;

					case (int)ePages.PAGE_GMAIL:
						m_GMailPage.Run();
						break;

					case (int)ePages.PAGE_RIVATUNER:
						m_RivaTunerPage.Run();
						break;
				}
			}
		}

		/// <summary>
		/// G19 buttons callback. Do nothing for now.
		/// </summary>
		/// <param name="_Sender">Event sender</param>
		/// <param name="_EventArgs">Event arguments</param>
		private void SoftButtonsCallback(object _Sender, LcdSoftButtonsEventArgs _EventArgs) {
			// CANCEL: Exit application
			if((_EventArgs.SoftButtons & LcdSoftButtons.Cancel) == LcdSoftButtons.Cancel) {
				Invoke(Shutdown);
			}

			// LEFT:
			else if((_EventArgs.SoftButtons & LcdSoftButtons.Left) == LcdSoftButtons.Left) {
				Invoke(new Action(
					delegate() {
						// Change active page:
						m_iCurrentPage--;
						if(m_iCurrentPage < 0) {
							m_iCurrentPage = (int)ePages.NUM_PAGES - 1;
						}
						m_LCDDevice.CurrentPage = m_vPages[m_iCurrentPage];
						AOC19NET.Properties.Settings.Default.CurrentPage = m_iCurrentPage;
						AOC19NET.Properties.Settings.Default.Save();
					}
				));
			}

			// RIGHT:
			else if((_EventArgs.SoftButtons & LcdSoftButtons.Right) == LcdSoftButtons.Right) {
				Invoke(new Action(
					delegate() {
						// Change active page:
						m_iCurrentPage++;
						if(m_iCurrentPage >= (int)ePages.NUM_PAGES) {
							m_iCurrentPage = 0;
						}
						m_LCDDevice.CurrentPage = m_vPages[m_iCurrentPage];
						AOC19NET.Properties.Settings.Default.CurrentPage = m_iCurrentPage;
						AOC19NET.Properties.Settings.Default.Save();
					}
				));
			}

			// MENU: Show Config Window
			else if((_EventArgs.SoftButtons & LcdSoftButtons.Menu) == LcdSoftButtons.Menu) {
				Invoke(new Action(
					delegate() {
						// Initialize the configuration window if it's not already done.
						if(m_ConfigWindow.Visibility == Visibility.Hidden) {
							m_ConfigWindow.LoadConfig();
							m_ConfigWindow.Visibility = Visibility.Visible;
						}
					}
				));
			}

			// UP: On the GMail page, scroll up
			else if((_EventArgs.SoftButtons & LcdSoftButtons.Up) == LcdSoftButtons.Up) {
				Invoke(new Action(
					delegate() {
						if(m_iCurrentPage == (int)ePages.PAGE_GMAIL) {
							m_GMailPage.Scroll(-1);
						}
					}
				));
			}

			// DOWN: On the GMail page, scroll down
			else if((_EventArgs.SoftButtons & LcdSoftButtons.Down) == LcdSoftButtons.Down) {
				Invoke(new Action(
					delegate() {
						if(m_iCurrentPage == (int)ePages.PAGE_GMAIL) {
							m_GMailPage.Scroll(1);
						}
					}
				));
			}

			// OK: On the GMail page, open browser
			else if((_EventArgs.SoftButtons & LcdSoftButtons.Ok) == LcdSoftButtons.Ok) {
				Invoke(new Action(
					delegate() {
						if(m_iCurrentPage == (int)ePages.PAGE_GMAIL) {
							m_GMailPage.StartBrowser();
						}
					}
				));
			}
		}
		#endregion
		#endregion

		/*-------------------------------------------------------------------------*/
		/* MAIN																	   */
		/*-------------------------------------------------------------------------*/
		[STAThread]
		internal static void Main() {
			// Create and run the applet
			AOC19 AOCApplication = new AOC19();
			try {
				AOCApplication.Run();
			} catch(Exception e) {
				Console.WriteLine();
				Console.WriteLine("BIG ERROR!!!!!!!");
				Console.WriteLine(e);
				Console.WriteLine();
			}
		}
	}
}
